#ifndef EPUBDUKPT_EPUB_DUKPT_HPP
#define EPUBDUKPT_EPUB_DUKPT_HPP

void *EpubDukptContextNew() {
  return new EpubContext();
}

void EpubDukptContextDestroy(void *ctx) {
  delete (reinterpret_cast<EpubContext *>(ctx));
}

void EpubDukptComponentA(void *ctx, unsigned char const *in, int inLen) {
  auto &&params = (*reinterpret_cast<EpubContext *>(ctx)).params();
  params.setComponentA(in, inLen);
}

void EpubDukptComponentB(void *ctx, unsigned char const *in, int inLen) {
  auto &&params = (*reinterpret_cast<EpubContext *>(ctx)).params();
  params.setComponentB(in, inLen);
}

void EpubDukptComponentC(void *ctx, unsigned char const *in, int inLen) {
  auto &&params = (*reinterpret_cast<EpubContext *>(ctx)).params();
  params.setComponentC(in, inLen);
}

void EpubDukptBDK(void *ctx, unsigned char const *in, int inLen) {
  auto &&params = (*reinterpret_cast<EpubContext *>(ctx)).params();
  params.setBDK(in, inLen);
}

bool EpubDukptGetBDK(void *ctx, unsigned char *in) {
  auto &&c      = *reinterpret_cast<EpubContext *>(ctx);
  auto &&params = c.params();

  auto &&ret = bool(true);
  auto &&zmk = params.zmk();
  auto &&bdk = params.bdk();

  if (bdk.empty())
    return false;

  if (zmk.empty()) {
    memcpy(in, bdk.data(), bdk.size());
  } else {
    auto &&key = c.createSchedule(zmk);
    for (int i = 0; i < zmk.size(); i += 8) {
      if (c.decrypt(key, &bdk[i], &in[i]))
        continue;
      ret = false;
      break;
    }
    c.destroySchedule(key);
  }

  return ret;
}

void EpubDukptZmkComponentA(void *ctx, unsigned char const *in, int inLen) {
  auto &&params = (*reinterpret_cast<EpubContext *>(ctx)).params();
  params.setZmkComponentA(in, inLen);
}

void EpubDukptZmkComponentB(void *ctx, unsigned char const *in, int inLen) {
  auto &&params = (*reinterpret_cast<EpubContext *>(ctx)).params();
  params.setZmkComponentB(in, inLen);
}

void EpubDukptZmkComponentC(void *ctx, unsigned char const *in, int inLen) {
  auto &&params = (*reinterpret_cast<EpubContext *>(ctx)).params();
  params.setZmkComponentC(in, inLen);
}

void EpubDukptZmk(void *ctx, unsigned char const *in, int inLen) {
  auto &&params = (*reinterpret_cast<EpubContext *>(ctx)).params();
  params.setZmk(in, inLen);
}

void EpubDukptKSI(void *ctx, unsigned char const *in, int inLen) {
  auto &&params = (*reinterpret_cast<EpubContext *>(ctx)).params();
  params.setKSI(in, inLen);
}

bool EpubDukptBDKKcv(void *ctx, unsigned char *out) {
  constexpr static unsigned char KCV[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

  auto &&ret = bool(true);
  auto &&c   = *reinterpret_cast<EpubContext *>(ctx);
  auto &&zmk = c.params().zmk();
  auto &&bdk = c.params().bdk();

  if (bdk.empty())
    return false;

  if (zmk.empty()) {
    auto &&key = c.createSchedule(bdk);
    for (int i = 0; i < bdk.size(); i += 8) {
      if (c.encrypt(key, &KCV[i], &out[i]))
        continue;
      ret = false;
      break;
    }
    c.destroySchedule(key);
  } else {
    auto &&tmp = std::vector<unsigned char>(bdk.size());
    auto &&tky = c.createSchedule(zmk);
    for (int i = 0; i < tmp.size(); i += 8) {
      if (c.decrypt(tky, &bdk[i], &tmp[i]))
        continue;
      ret = false;
      break;
    }
    c.destroySchedule(tky);

    if (!ret)
      return false;

    auto &&key = c.createSchedule(tmp);
    for (int i = 0; i < tmp.size(); i += 8) {
      if (c.encrypt(key, &KCV[i], &out[i]))
        continue;
      ret = false;
      break;
    }
    c.destroySchedule(key);
  }
  return ret;
}

bool EpubDukptZmkKcv(void *ctx, unsigned char *out) {
  constexpr static unsigned char KCV[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

  auto &&ret = bool(true);
  auto &&c   = *reinterpret_cast<EpubContext *>(ctx);
  auto &&zmk = c.params().zmk();

  if (zmk.empty())
    return false;

  auto &&key = c.createSchedule(zmk);
  for (int i = 0; i < zmk.size(); i += 8) {
    if (c.encrypt(key, &KCV[0], &out[i]))
      continue;
    ret = false;
    break;
  }
  c.destroySchedule(key);
  return ret;
}

bool EpubDukptKCV(void *ctx, unsigned char const *in, int inLen, unsigned char *out) {
  constexpr static unsigned char KCV[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

  auto &&ret = bool(false);
  auto &&con = *reinterpret_cast<EpubContext *>(ctx);
  if (inLen < sizeof(EpubDukpt::IKCV))
    return false;

  auto &&key = con.createSchedule(in, inLen);
  for (int i = 0; i < sizeof(KCV); i += 8) {
    if (con.encrypt(key, &KCV[i], &out[i]))
      continue;
    ret = false;
    break;
  }

  con.destroySchedule(key);
  return ret;
}

void *EpubDukptNew(void *ctx) {
  return new EpubDukpt(reinterpret_cast<EpubContext *>(ctx));
}

void EpubDukptDestroy(void *ptr) {
  delete (reinterpret_cast<EpubDukpt *>(ptr));
}

bool EpubDukptDisperse(void *ptr, unsigned char const *did, unsigned char *iksn,
  unsigned char *ipek, unsigned char *ikcv) {
  auto &&dukpt   = *reinterpret_cast<EpubDukpt *>(ptr);
  auto &&dataset = EpubDukpt::DataSet();
  auto &&ret     = dukpt.disperse(did, &dataset);
  memcpy(iksn, dataset.iksn(), dataset.iksnbytes());
  memcpy(ipek, dataset.ipek(), dataset.ipekbytes());
  memcpy(ikcv, dataset.ikcv(), dataset.ikcvbytes());
  return ret;
}

int EpubDukptIksnBytes() {
  return sizeof(EpubDukpt::IKSN);
}

int EpubDukptIpekBytes() {
  return sizeof(EpubDukpt::IPEK);
}

int EpubDukptIkcvBytes() {
  return sizeof(EpubDukpt::IKCV);
}

#endif//EPUBDUKPT_EPUB_DUKPT_HPP
